﻿#region MyJScript - LGPL Copyright (c) 2008 Lionel Laské
//
// This file is part of MyJScript.
//
// MyJScript is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// MyJScript is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MyJScript; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
//================================================================================
#endregion

using System;
using System.CodeDom;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Scripting;
using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Hosting;
using MSAst = Microsoft.Scripting.Ast;
using MyJScript.Compiler;
using MyJScript.DLR;
using MyJScript.Runtime;

namespace MyJScript.Test
{
    /// <summary>
    /// Unit testing for MyJScript compiler
    /// </summary>
    [TestClass]
    public class GeneratorTest
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public GeneratorTest()
        {
        }

        /// <summary>
        /// Set up DLR domain manager
        /// </summary>
        [TestInitialize]
        public void Initialize()
        {
            Utils.Initialize();
        }

        /// <summary>
        /// Delete temp files
        /// </summary>
        [TestCleanup]
        public void Cleanup()
        {
            Utils.Cleanup();
        }

        /// <summary>
        /// Empty source
        /// </summary>
        [TestMethod]
        public void Empty()
        {
            Parser p1 = new Parser(Utils.Source(null));
            Assert.IsTrue(p1.Parse());
            Assert.IsNotNull(p1.Result);
            Assert.AreEqual(0, p1.Result.Parameters.Count);
            Assert.AreEqual(0, p1.Result.Variables.Count);
            Assert.AreEqual("MyJScript", p1.Result.Name);
            Assert.AreEqual(typeof(MSAst.Block), p1.Result.Body.GetType());
            MSAst.Block block = (MSAst.Block)p1.Result.Body;
            Assert.AreEqual(0,block.Expressions.Count);

            Parser p2 = new Parser(Utils.Source(""));
            Assert.IsTrue(p2.Parse());
            Assert.IsNotNull(p2.Result);
            Assert.AreEqual(0, p2.Result.Parameters.Count);
            Assert.AreEqual(0, p2.Result.Variables.Count);
            Assert.AreEqual("MyJScript", p2.Result.Name);
            Assert.AreEqual(typeof(MSAst.Block), p2.Result.Body.GetType());
            block = (MSAst.Block)p2.Result.Body;
            Assert.AreEqual(0, block.Expressions.Count);
        }

        
        /// <summary>
        /// Test for functions
        /// </summary>
        [TestMethod]
        public void Functions()
        {
            // No parameter
            Parser p1 = new Parser(Utils.Source("function hello() {}"));
            Assert.IsTrue(p1.Parse());
            Assert.IsNotNull(p1.Result);
            Assert.AreEqual("MyJScript", p1.Result.Name);
            Assert.AreEqual(0, p1.Result.Parameters.Count);
            Assert.AreEqual(1, p1.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p1.Result.Body.GetType());

            MSAst.Block block = (MSAst.Block)p1.Result.Body;
            Assert.AreEqual(2, block.Expressions.Count);
            Assert.AreEqual("hello", SymbolTable.IdToString(p1.Result.Variables[0].Name));

            // hello=new MJSFunction(function hello() {});
            Assert.AreEqual(typeof(MSAst.ExpressionStatement), block.Expressions[0].GetType());
            MSAst.ExpressionStatement exprstmt = (MSAst.ExpressionStatement)block.Expressions[0];
            Assert.AreEqual(typeof(MSAst.BoundAssignment), exprstmt.Expression.GetType());
            MSAst.BoundAssignment assign = (MSAst.BoundAssignment)exprstmt.Expression;
            Assert.AreEqual(SymbolTable.StringToId("hello"), assign.Variable.Name);           
            Assert.AreEqual(typeof(MSAst.CodeBlockExpression), assign.Value.GetType());
            MSAst.CodeBlockExpression cbexpr = (MSAst.CodeBlockExpression)assign.Value;
            Assert.AreEqual(1, cbexpr.Block.Parameters.Count);
            Assert.AreEqual(SymbolTable.StringToId("this"), cbexpr.Block.Parameters[0].Name);
            Assert.AreEqual(typeof(MSAst.ReturnStatement), cbexpr.Block.Body.GetType());

            // MJSContext.Set("hello", hello);
            Assert.AreEqual(typeof(MSAst.ExpressionStatement), block.Expressions[1].GetType());
            exprstmt = (MSAst.ExpressionStatement)block.Expressions[1];
            Assert.AreEqual(typeof(MSAst.ActionExpression), exprstmt.Expression.GetType());
            MSAst.ActionExpression action = (MSAst.ActionExpression)exprstmt.Expression;
            Assert.AreEqual(DynamicActionKind.SetMember, action.Action.Kind);

            
            // One parameter
            Parser p2 = new Parser(Utils.Source("function oneparam(param_1) {}"));
            Assert.IsTrue(p2.Parse());
            Assert.IsNotNull(p2.Result);
            Assert.AreEqual("MyJScript", p2.Result.Name);
            Assert.AreEqual(0, p2.Result.Parameters.Count);
            Assert.AreEqual(1, p2.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p2.Result.Body.GetType());

            block = (MSAst.Block)p2.Result.Body;
            Assert.AreEqual(2, block.Expressions.Count);
            Assert.AreEqual("oneparam", SymbolTable.IdToString(p2.Result.Variables[0].Name));

            Assert.AreEqual(typeof(MSAst.ExpressionStatement), block.Expressions[0].GetType());
            exprstmt = (MSAst.ExpressionStatement)block.Expressions[0];
            Assert.AreEqual(typeof(MSAst.BoundAssignment), exprstmt.Expression.GetType());
            assign = (MSAst.BoundAssignment)exprstmt.Expression;
            Assert.AreEqual(SymbolTable.StringToId("oneparam"), assign.Variable.Name);
            Assert.AreEqual(typeof(MSAst.CodeBlockExpression), assign.Value.GetType());
            cbexpr = (MSAst.CodeBlockExpression)assign.Value;
            Assert.AreEqual(2, cbexpr.Block.Parameters.Count);
            Assert.AreEqual(SymbolTable.StringToId("this"), cbexpr.Block.Parameters[0].Name);
            Assert.AreEqual(SymbolTable.StringToId("param_1"), cbexpr.Block.Parameters[1].Name);
            Assert.AreEqual(typeof(MSAst.ReturnStatement), cbexpr.Block.Body.GetType());

            // Two parameters
            Parser p3 = new Parser(Utils.Source("function two_param(_p1, p3) {}"));
            Assert.IsTrue(p3.Parse());
            Assert.IsNotNull(p3.Result);
            Assert.AreEqual("MyJScript", p3.Result.Name);
            Assert.AreEqual(0, p3.Result.Parameters.Count);
            Assert.AreEqual(1, p3.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p3.Result.Body.GetType());

            block = (MSAst.Block)p3.Result.Body;
            Assert.AreEqual(2, block.Expressions.Count);
            Assert.AreEqual("two_param", SymbolTable.IdToString(p3.Result.Variables[0].Name));

            Assert.AreEqual(typeof(MSAst.ExpressionStatement), block.Expressions[0].GetType());
            exprstmt = (MSAst.ExpressionStatement)block.Expressions[0];
            Assert.AreEqual(typeof(MSAst.BoundAssignment), exprstmt.Expression.GetType());
            assign = (MSAst.BoundAssignment)exprstmt.Expression;
            Assert.AreEqual(SymbolTable.StringToId("two_param"), assign.Variable.Name);
            Assert.AreEqual(typeof(MSAst.CodeBlockExpression), assign.Value.GetType());
            cbexpr = (MSAst.CodeBlockExpression)assign.Value;
            Assert.AreEqual(3, cbexpr.Block.Parameters.Count);
            Assert.AreEqual(SymbolTable.StringToId("this"), cbexpr.Block.Parameters[0].Name);
            Assert.AreEqual(SymbolTable.StringToId("_p1"), cbexpr.Block.Parameters[1].Name);
            Assert.AreEqual(SymbolTable.StringToId("p3"), cbexpr.Block.Parameters[2].Name);
            Assert.AreEqual(typeof(MSAst.ReturnStatement), cbexpr.Block.Body.GetType());


            // Two parameters with same name
            Parser p4 = new Parser(Utils.Source("function sameNameParam(p1, p2, P3, p1)\r\n{\r\n}"));
            try
            {
                p4.Parse();
                Assert.Fail();
            }
            catch (SyntaxErrorException e)
            {
                Assert.AreEqual(400, e.ErrorCode);
                Assert.AreEqual(1, e.Line);
                Assert.AreEqual(39, e.Column);
            }

            // Multiple declaration for same function
            Parser p5 = new Parser(Utils.Source("function not_unique()\r\n{\r\n}\r\nfunction not_unique(p1)\r\n{\r\n}"));
            try
            {
                p5.Parse();
                Assert.Fail();
            }
            catch (SyntaxErrorException e)
            {
                Assert.AreEqual(401, e.ErrorCode);
                Assert.AreEqual(4, e.Line);
                Assert.AreEqual(24, e.Column);
            }

            // Return at global scope
            Parser p7 = new Parser(Utils.Source("while(1==1)\r\nreturn;"));
            try
            {
                p7.Parse();
                Assert.Fail();
            }
            catch (SyntaxErrorException e)
            {
                Assert.AreEqual(402, e.ErrorCode);
                Assert.AreEqual(2, e.Line);
                Assert.AreEqual(8, e.Column);
            }
        }

        /// <summary>
        /// Test for global variables
        /// </summary>
        [TestMethod]
        public void GlobalVariables()
        {
            // One variable
            Parser p1 = new Parser(Utils.Source("var v;"));
            Assert.IsTrue(p1.Parse());
            Assert.IsNotNull(p1.Result);
            Assert.AreEqual("MyJScript", p1.Result.Name);
            Assert.AreEqual(0, p1.Result.Parameters.Count);
            Assert.AreEqual(1, p1.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p1.Result.Body.GetType());

            MSAst.Block block = (MSAst.Block)p1.Result.Body;
            Assert.AreEqual(2, block.Expressions.Count);
            Assert.AreEqual("v", SymbolTable.IdToString(p1.Result.Variables[0].Name));


            // Multiple declaration of same var
            Parser p2 = new Parser(Utils.Source("var x;\r\nvar x=45;"));
            Assert.IsTrue(p2.Parse());
            Assert.IsNotNull(p2.Result);
            Assert.AreEqual("MyJScript", p2.Result.Name);
            Assert.AreEqual(0, p2.Result.Parameters.Count);
            Assert.AreEqual(1, p2.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p2.Result.Body.GetType());

            block = (MSAst.Block)p2.Result.Body;
            Assert.AreEqual(2, block.Expressions.Count);
            Assert.AreEqual(typeof(MSAst.Block), block.Expressions[0].GetType());
            MSAst.Block subblock = (MSAst.Block)block.Expressions[0];
            Assert.AreEqual(2, subblock.Expressions.Count);
            Assert.AreEqual(typeof(MSAst.Block), block.Expressions[1].GetType());
            subblock = (MSAst.Block)block.Expressions[1];
            Assert.AreEqual(2, subblock.Expressions.Count);

            Assert.AreEqual("x", SymbolTable.IdToString(p2.Result.Variables[0].Name));

             // Two vars
            Parser p3 = new Parser(Utils.Source("var v1;\r\nfunction foo() { }\r\nvar v2='Hello!';"));
            Assert.IsTrue(p3.Parse());
            Assert.IsNotNull(p3.Result);
            Assert.AreEqual("MyJScript", p3.Result.Name);
            Assert.AreEqual(0, p3.Result.Parameters.Count);
            Assert.AreEqual(3, p3.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p3.Result.Body.GetType());

            block = (MSAst.Block)p3.Result.Body;
            Assert.AreEqual(3, block.Expressions.Count);
            Assert.AreEqual("v1", SymbolTable.IdToString(p3.Result.Variables[0].Name));
            Assert.AreEqual("foo", SymbolTable.IdToString(p3.Result.Variables[1].Name));
            Assert.AreEqual("v2", SymbolTable.IdToString(p3.Result.Variables[2].Name));
        }

        /// <summary>
        /// Test for local variables
        /// </summary>
        [TestMethod]
        public void LocalVariables()
        {
            // One variable
            Parser p1 = new Parser(Utils.Source("function foo() { var v; }"));
            Assert.IsTrue(p1.Parse());
            Assert.IsNotNull(p1.Result);
            Assert.AreEqual("MyJScript", p1.Result.Name);
            Assert.AreEqual(0, p1.Result.Parameters.Count);
            Assert.AreEqual(1, p1.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p1.Result.Body.GetType());

            MSAst.Block block = (MSAst.Block)p1.Result.Body;
            Assert.AreEqual(2, block.Expressions.Count);
            Assert.AreEqual("foo", SymbolTable.IdToString(p1.Result.Variables[0].Name));

            Assert.AreEqual(typeof(MSAst.ExpressionStatement), block.Expressions[0].GetType());
            MSAst.ExpressionStatement exprstmt = (MSAst.ExpressionStatement)block.Expressions[0];
            Assert.AreEqual(typeof(MSAst.BoundAssignment), exprstmt.Expression.GetType());
            MSAst.BoundAssignment assign = (MSAst.BoundAssignment)exprstmt.Expression;
            Assert.AreEqual(SymbolTable.StringToId("foo"), assign.Variable.Name);
            Assert.AreEqual(typeof(MSAst.CodeBlockExpression), assign.Value.GetType());
            MSAst.CodeBlockExpression cbexpr = (MSAst.CodeBlockExpression)assign.Value;
            Assert.AreEqual(1, cbexpr.Block.Parameters.Count);
            Assert.AreEqual(SymbolTable.StringToId("this"), cbexpr.Block.Parameters[0].Name);
            Assert.AreEqual(1, cbexpr.Block.Variables.Count);
            Assert.AreEqual(SymbolTable.StringToId("v"), cbexpr.Block.Variables[0].Name);

            // Nested variable but same scope
            Parser p2 = new Parser(Utils.Source("function foo() { var v; while(2==0) { var w=3; } }"));
            Assert.IsTrue(p2.Parse());
            Assert.IsNotNull(p2.Result);
            Assert.AreEqual("MyJScript", p2.Result.Name);
            Assert.AreEqual(0, p2.Result.Parameters.Count);
            Assert.AreEqual(1, p2.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p2.Result.Body.GetType());

            block = (MSAst.Block)p2.Result.Body;
            Assert.AreEqual(2, block.Expressions.Count);
            Assert.AreEqual("foo", SymbolTable.IdToString(p2.Result.Variables[0].Name));

            Assert.AreEqual(typeof(MSAst.ExpressionStatement), block.Expressions[0].GetType());
            exprstmt = (MSAst.ExpressionStatement)block.Expressions[0];
            Assert.AreEqual(typeof(MSAst.BoundAssignment), exprstmt.Expression.GetType());
            assign = (MSAst.BoundAssignment)exprstmt.Expression;
            Assert.AreEqual(SymbolTable.StringToId("foo"), assign.Variable.Name);
            Assert.AreEqual(typeof(MSAst.CodeBlockExpression), assign.Value.GetType());
            cbexpr = (MSAst.CodeBlockExpression)assign.Value;
            Assert.AreEqual(1, cbexpr.Block.Parameters.Count);
            Assert.AreEqual(SymbolTable.StringToId("this"), cbexpr.Block.Parameters[0].Name);
            Assert.AreEqual(2, cbexpr.Block.Variables.Count);
            Assert.AreEqual(SymbolTable.StringToId("v"), cbexpr.Block.Variables[0].Name);
            Assert.AreEqual(SymbolTable.StringToId("w"), cbexpr.Block.Variables[1].Name);


            // Local scope conflict with global scope
            Parser p3 = new Parser(Utils.Source("var v='MJS'; function foo() { var v='Go!'; }"));
            Assert.IsTrue(p3.Parse());
            Assert.IsNotNull(p3.Result);

            Assert.AreEqual("MyJScript", p3.Result.Name);
            Assert.AreEqual(0, p3.Result.Parameters.Count);
            Assert.AreEqual(2, p3.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p3.Result.Body.GetType());

            block = (MSAst.Block)p3.Result.Body;
            Assert.AreEqual(2, block.Expressions.Count);
            Assert.AreEqual("v", SymbolTable.IdToString(p3.Result.Variables[0].Name));
            Assert.AreEqual("foo", SymbolTable.IdToString(p3.Result.Variables[1].Name));
            MSAst.Block block2 = (MSAst.Block)block.Expressions[1];
            exprstmt = (MSAst.ExpressionStatement)block2.Expressions[0];
            Assert.AreEqual(typeof(MSAst.BoundAssignment), exprstmt.Expression.GetType());
            assign = (MSAst.BoundAssignment)exprstmt.Expression;
            Assert.AreEqual(SymbolTable.StringToId("foo"), assign.Variable.Name);
            Assert.AreEqual(typeof(MSAst.CodeBlockExpression), assign.Value.GetType());
            cbexpr = (MSAst.CodeBlockExpression)assign.Value;
            Assert.AreEqual(1, cbexpr.Block.Parameters.Count);
            Assert.AreEqual(SymbolTable.StringToId("this"), cbexpr.Block.Parameters[0].Name);
            Assert.AreEqual(1, cbexpr.Block.Variables.Count);
            Assert.AreEqual(SymbolTable.StringToId("v"), cbexpr.Block.Variables[0].Name);


            // Local scope conflict with parameter, don't declare variable
            Parser p4 = new Parser(Utils.Source("function foo(a) { var a=100; }"));
            Assert.IsTrue(p4.Parse());
            Assert.IsNotNull(p4.Result);
            Assert.AreEqual("MyJScript", p3.Result.Name);
            Assert.AreEqual(0, p4.Result.Parameters.Count);
            Assert.AreEqual(1, p4.Result.Variables.Count);
            Assert.AreEqual(typeof(MSAst.Block), p4.Result.Body.GetType());

            block = (MSAst.Block)p4.Result.Body;
            Assert.AreEqual(2, block.Expressions.Count);
            Assert.AreEqual("foo", SymbolTable.IdToString(p4.Result.Variables[0].Name));

            Assert.AreEqual(typeof(MSAst.ExpressionStatement), block.Expressions[0].GetType());
            exprstmt = (MSAst.ExpressionStatement)block.Expressions[0];
            Assert.AreEqual(typeof(MSAst.BoundAssignment), exprstmt.Expression.GetType());
            assign = (MSAst.BoundAssignment)exprstmt.Expression;
            Assert.AreEqual(SymbolTable.StringToId("foo"), assign.Variable.Name);
            Assert.AreEqual(typeof(MSAst.CodeBlockExpression), assign.Value.GetType());
            cbexpr = (MSAst.CodeBlockExpression)assign.Value;
            Assert.AreEqual(2, cbexpr.Block.Parameters.Count);
            Assert.AreEqual(SymbolTable.StringToId("this"), cbexpr.Block.Parameters[0].Name);
            Assert.AreEqual(SymbolTable.StringToId("a"), cbexpr.Block.Parameters[1].Name);
        }
    }
}
